package core;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

public class XmlEngine {

	static private XmlEngine instance = null;
	static private String[] defaultCategory = { "Food", "House", "Gift",
			"Vacation", "Work", "Child", "Pet" };
	private org.jdom.Document document = null;
	private Element root = null;
	private Element categoryNode = null;
	private Element entryNode = null;
	private Element noteNode = null;
	private ArrayList<Category> categories = new ArrayList<Category>();
	private ArrayList<UserEntry> entries = new ArrayList<UserEntry>();
	private String DefaultPath = new String("data.xml");
	private HashMap<Integer, Category> CategoryMap = new HashMap<Integer, Category>();

	private class CompareEntry implements Comparator<UserEntry> {
		public int compare(UserEntry o1, UserEntry o2) {
			if (o1.getDate().before(o2.getDate()))
				return -1;
			if (o1.getDate().after(o2.getDate()))
				return 1;
			return 0;
		}
	}

	private XmlEngine() {
	}

	static public XmlEngine get() {
		if (instance == null)
			instance = new XmlEngine();
		return instance;
	}

	static public void kill() {
		instance = null;
	}

	public void load() {
		load(DefaultPath);
	}

	public void save() {
		save(DefaultPath);
	}

	public void load(String path) {
		CategoryMap.clear();
		if (path == null)
			return;

		SAXBuilder sxb = new SAXBuilder();
		try {
			document = sxb.build(new File(path));
		} catch (Exception e) {
		}
		root = document.getRootElement();

		@SuppressWarnings("unchecked")
		List<Element> listCategory = root.getChildren("categories");
		Iterator<Element> i1 = listCategory.iterator();
		while (i1.hasNext()) {
			parseCategories(i1.next());
		}
		@SuppressWarnings("unchecked")
		List<Element> listEntry = root.getChildren("entries");
		Iterator<Element> i2 = listEntry.iterator();
		while (i2.hasNext()) {
			parseEntries(i2.next());
		}
		Collections.sort(entries, new CompareEntry());

		noteNode = root.getChild("note");
		if (noteNode == null) {
			noteNode = new Element("note");
			root.addContent(noteNode);
		}
		save();
	}

	private void parseCategories(Element categoriesNode) {
		categoryNode = categoriesNode;
		@SuppressWarnings("unchecked")
		List<Element> listCategory = categoriesNode.getChildren("category");
		Iterator<Element> i = listCategory.iterator();
		while (i.hasNext()) {
			Element cur = i.next();
			String catId = cur.getAttributeValue("id");
			if (catId == null) {
				System.err
						.println("Error reading xml: no id field in category");
				continue;
			}
			int id = 0;
			try {
				id = Integer.parseInt(catId);
			} catch (Exception e) {
				System.err.println("Error reading xml: invalid id field");
				continue;
			}
			String name = cur.getAttributeValue("name");
			name = (name == null) ? new String("unknow") : name;
			Category newCat = new Category(cur, id, name);
			categories.add(newCat);
			CategoryMap.put(id, newCat);
		}
	}

	private void parseEntries(Element entiresNode) {
		entryNode = entiresNode;
		@SuppressWarnings("unchecked")
		List<Element> listEntry = entryNode.getChildren("entry");
		Iterator<Element> i = listEntry.iterator();
		while (i.hasNext()) {
			Element cur = i.next();
			UserEntry curEntry = new UserEntry(cur);
			// Parse catId
			String catId = cur.getAttributeValue("catid");
			if (catId == null) {
				System.err
						.println("Error reading xml: no catid field in entry");
				continue;
			}
			try {
				int id = Integer.parseInt(catId);
				curEntry.setCategoryId(id);
			} catch (Exception e) {
				System.err.println("Error reading xml: invalid catid field");
				continue;
			}
			// Parse Value
			String valueStr = cur.getAttributeValue("value");
			if (valueStr == null) {
				System.err
						.println("Error reading xml: no value field in entry");
				continue;
			}
			try {
				float value = Float.parseFloat(valueStr);
				curEntry.setValue(value);
			} catch (Exception e) {
				System.err.println("Error reading xml: invalid value field");
				continue;
			}
			// Parse Date
			String dateStr = cur.getAttributeValue("date");
			if (dateStr == null) {
				System.err.println("Error reading xml: no date field in entry");
				continue;
			}
			try {
				SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
				Date date = df.parse(dateStr);
				curEntry.setDate(date);
			} catch (Exception ex) {
				System.err
						.println("Error reading xml: invalid date field in entry");
				continue;
			}
			// Parse Comment
			curEntry.setComment(cur.getAttributeValue("comment"));
			entries.add(curEntry);
		}
	}

	public void save(String path) {
		Collections.sort(entries, new CompareEntry());
		try {
			XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
			out.output(document, new FileOutputStream(path));
		} catch (java.io.IOException e) {
		}
	}

	public Category addCategory(String name) {
		return addCategory(name, true);
	}

	private Category addCategory(String name, boolean saveXml) {
		// look for a new id
		boolean searching = true;
		int id = 0;
		while (searching) {
			id = (int) (Math.random() * 100000000) + 1;
			searching = false;
			Iterator<Category> i = categories.iterator();
			while (i.hasNext()) {
				Category cur = i.next();
				if (cur.getName().equals(name))
					return cur;
				if (cur.getId() == id) {
					searching = true;
					break;
				}
			}
		}
		// add it
		if (categoryNode == null) {
			categoryNode = new Element("categories");
			if (root == null) {
				root = new Element("data");
				document = new Document(root);
			}
			root.addContent(categoryNode);
		}
		Element newNode = new Element("category");
		categoryNode.addContent(newNode);
		Category newCat = new Category(newNode, id, name);
		categories.add(newCat);
		CategoryMap.put(id, newCat);
		if (saveXml)
			newCat.save();
		return newCat;
	}

	public UserEntry addEntry(float value, String comment, int catId, Date date) {
		if (entryNode == null) {
			entryNode = new Element("entries");
			if (root == null) {
				root = new Element("data");
				document = new Document(root);
			}
			root.addContent(entryNode);
		}
		Element newNode = new Element("entry");
		entryNode.addContent(newNode);
		UserEntry newEntry = new UserEntry(newNode);
		newEntry.setCategoryId(catId);
		newEntry.setComment(comment);
		newEntry.setValue(value);
		newEntry.setDate(date);

		// Chercher la position ou inserer
		int pos = 0;
		Iterator<UserEntry> i = entries.iterator();
		while (i.hasNext()) {
			UserEntry cur = i.next();
			if (date.before(cur.getDate()))
				break;
			pos++;
		}
		if (pos == entries.size())
			entries.add(newEntry);
		else
			entries.add(pos, newEntry);
		newEntry.save();
		return newEntry;
	}

	public void reset() {
		CategoryMap.clear();
		root = new Element("data");
		document = new Document(root);
		noteNode = new Element("note");
		root.addContent(noteNode);
		for (int i = 0; i < defaultCategory.length; ++i)
			addCategory(defaultCategory[i]);

		save();
	}

	public void setNote(String newNote) {
		if (noteNode == null)
			System.err.println("Error: No noteNode...");
		else {
			if (newNote == null)
				newNote = new String();
			noteNode.setText(newNote);
			save();
		}
	}

	public String getNote() {
		if (noteNode == null) {
			System.err.println("Error: No noteNode...");
			return null;
		}
		return noteNode.getText();
	}

	public void deleteCategory(int id) {
		deleteCategory(id, 0);
	}

	public void deleteCategory(Category cat) {
		deleteCategory(cat.getId(), 0);
	}

	public void deleteCategory(Category cat, int newId) {
		deleteCategory(cat.getId(), newId);
	}

	public void deleteCategory(int id, int newId) {
		int delId = 0;
		Iterator<Category> i = categories.iterator();
		while (i.hasNext()) {
			Category cur = i.next();
			if (cur.getId() == id)
				break;
			delId += 1;
		}
		if (delId < categories.size()) {
			categories.get(delId).detach();
			categories.remove(delId);
			for (int eId = 0; eId < entries.size(); ++eId)
				if (entries.get(eId).getCategoryId() == id) {
					entries.get(eId).setCategoryId(newId);
					entries.get(eId).save(false);
				}
			CategoryMap.remove(id);
			save();
		}
	}

	public void deleteEntry(UserEntry delEntry) {
		int delId = entries.indexOf(delEntry);
		if (delId >= 0) {
			entries.get(delId).detach();
			entries.remove(delId);
			save();
		}
	}

	public List<UserEntry> getEntries() {
		return entries;
	}

	public List<Category> getCategories() {
		return categories;
	}

	public String getCategoryName(int id) {
		if (!CategoryMap.containsKey(id))
			return null;
		return CategoryMap.get(id).getName();
	}
}
